package com.yricky.astral.structure.scope

import com.yricky.astral.lib.Const.BIT_MASK
import com.yricky.astral.lib.Const.HEX
import com.yricky.astral.lib.Const.RADIX_CHAR
import com.yricky.astral.project.Hazuki
import com.yricky.astral.signal.AssignableSignal
import com.yricky.astral.signal.Signal

interface SignalUtils:Hazuki.Checkable {
    abstract class InnerSignal: Signal {
        override val tags: MutableSet<String> = mutableSetOf(Signal.TAG_INNER)
    }

    /**
     * Split signal.
     *
     * Example:
     *
     * Astral code: wire("data_o",8).split(5,0)
     *
     * Generated verilog code: data_o`[5:0`]
     */
    fun AssignableSignal.split(l:Int, r:Int = l):Signal{
        if(l<r)
            throw IllegalArgumentException("Size of slice must larger than 0!")
        if((l>width-1) or (l<0) or (r>width-1) or (r<0))
            throw IllegalArgumentException("Split of ${generateBody()} out of bound!([$l:$r] for a $width width signal)")
        return object :InnerSignal(){
            override val standard: Hazuki.HazukiStandard = this@SignalUtils.standard
            override val width: Int = l-r+1
            override fun generateBody(): String {
                return "$name[$l:$r]"
            }
        }
    }

    /**
     * generate const signal with width = w.
     *
     * Example:
     *
     * Astral code: 17.w(8)
     *
     * Generated verilog code: 8'h11
     *
     * @see Long.w
     */
    fun Int.w(w:Int = bitWidth(),radix:Int = HEX):Signal{
        return toLong().w(w,radix)
    }
    fun Long.w(w:Int = bitWidth(),radix:Int = HEX):Signal{
        val v = this and BIT_MASK[w]
        return object :InnerSignal(){
            override val standard: Hazuki.HazukiStandard = this@SignalUtils.standard
            override val width: Int = w
            override val sValue: Number = v
            override fun generateBody(): String = "$w'${RADIX_CHAR[radix]}${v.toString(radix)}"
        }
    }

    /**
     * Joint signals to one signal.
     *
     * Example:
     *
     * Astral code: joint(1.w(2),2.w(2))
     *
     * Generated verilog code: {{2'h1},{2'h2}}
     */
    fun joint(vararg signals:Signal):Signal{
        return joint(signals.asList())
    }
    fun joint(signals:List<Signal>):Signal{
        if(signals.isEmpty())
            throw IllegalStateException("Empty signal list!")
        if(signals.size == 1)
            throw IllegalStateException("Please use ${signals.first().generateBody()} directly!")
        var w = 0
        val sb = StringBuilder()
        sb.append("{")
        signals.forEachIndexed { index,it->
            w += it.width
            sb.append("{${it.generateBody()}}")
            if(index != signals.size-1){
                sb.append(",")
            }
        }
        sb.append("}")
        return object :InnerSignal(){
            override val standard: Hazuki.HazukiStandard = this@SignalUtils.standard
            override val width: Int = w
            override fun generateBody(): String =sb.toString()
        }
    }


    /**
     * Minimum bit width of an integer.
     *
     * Example:
     *
     * 5.bitWidth() = 3  (because 5=0b101)
     */
    fun Int.bitWidth():Int{
        if(this<0)
            return 32
        BIT_MASK.forEachIndexed { index, l ->
            if(l>this)
                return index.coerceAtLeast(1)
        }
        return 32
    }

    /**
     * @see Int.bitWidth
     */
    fun Long.bitWidth():Int{
        if(this<0)
            return 64
        BIT_MASK.forEachIndexed { index, l ->
            if(l>this)
                return index
        }
        return 64
    }
}
